package it.eng.ws.test.configuration;

import java.io.IOException;
import java.util.HashMap;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.SchemaFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import cern.jet.random.Uniform;

import it.eng.ws.test.automata.Automata;
import it.eng.ws.test.automata.State;
import it.eng.ws.test.automata.StateAction;
import it.eng.ws.test.automata.StateLink;
import it.eng.ws.test.automata.TransitionDelayType;
import it.eng.ws.test.exceptions.UAConfigurationException;
import it.eng.ws.test.logging.LoggingSample;
import it.eng.ws.test.logging.SimpleLog;

public class ConfigurationManager {

	private final static int 		DEFAULT_USER_AGENTS = 10;
	private final static int 		DEFAULT_TEST_DURATION = 3600000;
	
	private static ConfigurationManager instance = null;
	private static UAConfigurationException configurationException = null;
	private static Logger l = null;

	
	static{
		if (instance == null)
			try {
				instance = new ConfigurationManager();
			} catch (UAConfigurationException e) {
				configurationException = e;
			}
	}
	
	
	private Automata automata = null;
	private int concurrentUserAgents = -1;
	private long testDuration = -1;
	private HashMap<Thread, Uniform> randomGenerators = null;
	private HashMap<Thread, HashMap<String, Object>> sharedMemory = null;	
	private boolean expiredTime = false;
	
	private Runnable expireTimeWatcher = new Runnable(){

		@Override
		public void run() {
			try {
				Thread.sleep(testDuration);
				expiredTime = true;
			} catch (InterruptedException e) {
				l.error("Error in TimeWatcher thread", e);
			}
			
		}
		
	};
	
	private ConfigurationManager () throws UAConfigurationException{
		if (l == null)
			l = LoggingSample.getLogger(it.eng.ws.test.configuration.ConfigurationManager.class);
		sharedMemory=new HashMap<Thread, HashMap<String, Object>>();
		randomGenerators=new HashMap<Thread, Uniform>();
	}
	
	public void startConfiguration(String namespace, String xmlSchema, String xml) throws UAConfigurationException{
		XPathFactory fac = XPathFactory.newInstance();
		Document configurationDocument = null;
		XPath xpath = fac.newXPath();
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		factory.setValidating(false);
		factory.setNamespaceAware(true);
		SchemaFactory schemaFactory =  SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
		try {
			factory.setSchema(schemaFactory.newSchema(new Source[] {new StreamSource(Thread.currentThread().getContextClassLoader().getResourceAsStream(xmlSchema))}));
			DocumentBuilder builder = factory.newDocumentBuilder();
			builder.setErrorHandler(new SimpleErrorHandler());
			configurationDocument = builder.parse(new InputSource(Thread.currentThread().getContextClassLoader().getResourceAsStream(xml)));
			xpath.setNamespaceContext(new PersonalNamespaceContext(namespace));
		} catch (ParserConfigurationException e) {
			l.error(e.getMessage(),e);
		} catch (SAXException e) {
			l.error(e.getMessage(),e);
		} catch (IOException e) {
			l.error(e.getMessage(),e);
		}
		concurrentUserAgents = loadConcurrentUserAgents(configurationDocument, xpath);
		testDuration = loadTestDuration(configurationDocument, xpath);
		automata = loadAutomata(configurationDocument, xpath);

		if (automata.getInitialState()==null)
			throw new UAConfigurationException("The automata has no initial state");

		automata.checkConsistency();
		SimpleLog.getInstance().setLogFilename("test-"+concurrentUserAgents+"-Thread.txt");
		new Thread(expireTimeWatcher).start();
		
	}
	
	private int loadConcurrentUserAgents(Document configurationDocument, XPath xpath){
		int concurrentUserAgents = -1;
		if(configurationDocument!=null){
			try {
				XPathExpression expr = xpath.compile("//ns:configuration/ns:concurrentUserAgents/text()");
				Double result = (Double)expr.evaluate(configurationDocument, XPathConstants.NUMBER);  
				if(result!=null)
					concurrentUserAgents=result.intValue();
			} catch (XPathExpressionException e) {
				l.error(e.getMessage(),e);
			}
		}
		if (concurrentUserAgents==-1)
			concurrentUserAgents = DEFAULT_USER_AGENTS;

		return concurrentUserAgents;
	}
	
	private int loadTestDuration(Document configurationDocument, XPath xpath){
		int testDuration = -1;
		if(configurationDocument!=null){
			try {
				XPathExpression expr  = xpath.compile("//ns:configuration/ns:testDuration/text()");
				Double result = (Double)expr.evaluate(configurationDocument, XPathConstants.NUMBER);
				if(result!=null)
					testDuration=result.intValue();
			} catch (XPathExpressionException e) {
				l.error(e.getMessage(),e);
			}
		}
		if (testDuration==-1)
			testDuration = DEFAULT_TEST_DURATION;

		return testDuration;
	}
	
	private Automata loadAutomata(Document configurationDocument, XPath xpath) throws UAConfigurationException{
		automata = Automata.getInstance();
		try {
			XPathExpression expr = xpath.compile("//ns:configuration/ns:userAgent/ns:behavior/ns:state");
			Object result = expr.evaluate(configurationDocument, XPathConstants.NODESET);
			NodeList nodes = (NodeList) result;
			for (int i = 0; i < nodes.getLength(); i++) {
				State s=new State();
				Node state=nodes.item(i);
				expr  = xpath.compile("@label");
				result = expr.evaluate(state, XPathConstants.STRING);
				
				if(result!=null && (result instanceof String) && !(((String)result).equals("")))
					s.setLabel((String)result);

				expr  = xpath.compile("@type");
				result = expr.evaluate(state, XPathConstants.STRING);
				String type=(String)result;
				if(type.equals("initial"))
					s.setInitialState(true);
				else if(type.equals("final"))
					s.setFinalState(true);
				
				expr  = xpath.compile("ns:stateBehavior/ns:stateAction");
				result = expr.evaluate(state, XPathConstants.NODESET);
				NodeList nodesStateAction = (NodeList) result;
				for (int j = 0; j < nodesStateAction.getLength(); j++) {
					Node stateAction=nodesStateAction.item(j);
					expr  = xpath.compile("@class");
					result = expr.evaluate(stateAction, XPathConstants.STRING);

					if(result!=null && (result instanceof String) && !(((String)result).equals(""))){
						StateAction sa = getStateActionInstance((String)result);

						expr  = xpath.compile("ns:stateActionParam");
						result = expr.evaluate(stateAction, XPathConstants.NODESET);
						NodeList nodesStateActionParam = (NodeList) result;
						for (int k = 0; k < nodesStateActionParam.getLength(); k++) {
							Node stateActionParam=nodesStateActionParam.item(j);

							expr  = xpath.compile("@name");
							Object stateActionName = expr.evaluate(stateActionParam, XPathConstants.STRING);
							expr  = xpath.compile("@type");
							//Object stateActionType = expr.evaluate(stateActionParam, XPathConstants.STRING);
							
							if(stateActionName!=null && (stateActionName instanceof String) && !(((String)stateActionName).equals("")) 
								//	&& stateActionType!=null && (stateActionType instanceof String) && !(((String)stateActionType).equals(""))
									&& stateActionParam.getTextContent()!=null)
								sa.addParam((String)stateActionName,stateActionParam.getTextContent());
						}
						l.info("inserisco la classe "+sa.getClass().getName());
						s.addStateAction(sa);
					}
				}

				expr  = xpath.compile("ns:stateTransitions/ns:stateLink");
				result = expr.evaluate(state, XPathConstants.NODESET);
				NodeList nodesStateLink = (NodeList) result;
				for (int j = 0; j < nodesStateLink.getLength(); j++) {
					Node stateLink=nodesStateLink.item(j);
					StateLink sl = new StateLink();
					expr  = xpath.compile("@label");
					result = expr.evaluate(stateLink, XPathConstants.STRING);
					if(result!=null && (result instanceof String) && !(((String)result).equals("")))
						sl.setLabel((String)result);

					expr  = xpath.compile("ns:likelyhood/text()");
					Double d = (Double)expr.evaluate(stateLink, XPathConstants.NUMBER);
					if(result!=null)
						sl.setLikelyhood(d.floatValue());

					expr  = xpath.compile("ns:meanTransitionDelay/text()");
					d = (Double)expr.evaluate(stateLink, XPathConstants.NUMBER);
					if(result!=null)
						sl.setMeanTransitionDelay(d.longValue());

					expr  = xpath.compile("ns:meanTransitionDelay/@type");
					result = expr.evaluate(stateLink, XPathConstants.STRING);
					if(result!=null && (result instanceof String) && !(((String)result).equals(""))){
						if ("fixed".equals((String)result))
							sl.setDelayType(TransitionDelayType.fixed);
						else if ("random".equals((String)result))
							sl.setDelayType(TransitionDelayType.random);
					} else 
						sl.setDelayType(TransitionDelayType.random);
					expr  = xpath.compile("ns:targetState/text()");
					result = expr.evaluate(stateLink, XPathConstants.STRING);
					if(result!=null && (result instanceof String) && !(((String)result).equals("")))
						sl.setTargetState((String)result);
					s.addStateLink(sl);
				}
				automata.addState(s);
			}
		} catch (XPathExpressionException e) {
			l.error(e.getMessage(),e);
		} 
		return automata;
	}
	
	private StateAction getStateActionInstance(String className) throws UAConfigurationException{
		StateAction stateAction = null;
		
		try{
			Class stateActionClass = ClassLoader.getSystemClassLoader().loadClass(className);
			stateAction = (StateAction) stateActionClass.newInstance();
		} catch (Exception e) {
			l.error(e.getMessage(),e);
			throw new UAConfigurationException("Error instantiating StateAction class: " + className);
		}
		
		return stateAction;
	}
	
	public static ConfigurationManager getInstance() throws UAConfigurationException{
		if (configurationException!=null)
			throw new UAConfigurationException("Configuration error: please see the cause", configurationException);
		
		return instance;
	}
	
	public int getConcurrentUserAgents(){
		return concurrentUserAgents;
	}
	
	public long getTestDuration(){
		return testDuration;
	}
	
	public Automata getAutomata(){
		return Automata.getInstance();
	}
	
	public boolean isExpiredTime() {
		return expiredTime;
	}

	public void setExpiredTime(boolean expiredTime) {
		this.expiredTime = expiredTime;
	}

	public synchronized Uniform getUDRandomGenerator(){
		Uniform uniform = randomGenerators.get(Thread.currentThread());
		
		if (uniform==null){
			uniform = new Uniform(0,1,Thread.currentThread().hashCode());
			randomGenerators.put(Thread.currentThread(), uniform);
		}
		
		return uniform;
	}
	
	public synchronized HashMap<String, Object> getSharedMemory(){
		HashMap<String, Object>  shared = sharedMemory.get(Thread.currentThread());
		
		if (shared==null){
			shared = new HashMap<String, Object>();
			sharedMemory.put(Thread.currentThread(), shared);
		}
		
		return shared;
	}
}
